import "babel-polyfill";
import {reduce} from "../../algorithm/iterator";
import Integer from "../number/integer";
import factor from "./factor";

const {one, eq, compare, abs, subtract, multiply, mod, exactDivide, pow} = Integer;

export var jacobi = function(m, n) {
    const three_or_five = {"3": true, "5": true};

    m = Integer.from(m);
    n = Integer.from(n);
    if (m.sign === 0) {
        return 0;
    } else if (n.sign === 0) {
        return eq(m, one) ? 1 : 0;
    }
    var [s1, x] = abs(m).extractEven();
    var [s2, y] = abs(n).extractEven();
    if (s1 && s2) {
        return 0;
    }
    var k = n.sign > 0 ? 1 : -1;
    if (m.sign < 0 && (y.lastLimb & 2)) {
        k = -k;
    }
    if (s1 & 1 && (y.lastLimb & 7) in three_or_five) {
        k = -k;
    }
    if (s2 & 1 && (x.lastLimb & 7) in three_or_five) {
        k = -k;
    }

    var comparison = compare(x, y);
    if (comparison === 0) {
        return 0;
    } else if (comparison > 0) {
        if (x.lastLimb & y.lastLimb & 2) {
            k = -k;
        }
        [x, y] = [y, x];
    }

    while (x) {
        if (eq(x, one)) {
            return k;
        }
        if (x.lastLimb & y.lastLimb & 2) {
            k = -k;
        }
        [x, y] = [mod(y, x), x];
        if (x.sign === 0) {
            return 0;
        }
        let s;
        [s, x] = x.extractEven();
        if (s & 1 && (y.lastLimb & 7) in three_or_five) {
            k = -k;
        }
    }
};

export var divisor = function(n, exponent = 1) {
    if (exponent === 0) {
        return factor(n).reduce(function(result, [, e]) {
            return multiply(result, e + 1);
        }, one);
    } else {
        return factor(n)::reduce(function(result, [p, e]) {
            return multiply(result, exactDivide(
                subtract(pow(p, exponent * (e + 1)), one),
                subtract(pow(p, exponent), one)
            ));
        }, one);
    }
};

export var eulerTotient = function(n, exp = 1) {
    return factor(n)::reduce(function(result, [p]) {
        return subtract(result, exactDivide(result, pow(p, exp)));
    }, pow(n, exp));
};

export var moebius = function(n) {
    var f = [...factor(n)];
    if (f.some(([, e]) => e > 1)) {
        return 0;
    } else {
        return f.length & 1 ? -1 : 1;
    }
};
